/**
 * 
 */
package com.pxingg.equipment.powerswitch.Sevytec;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.pxingg.datetime.TimeUtils;
import com.pxingg.datetime.Timer;
import com.pxingg.encrypt.crc.CRC;
import com.pxingg.exception.XException;
import com.pxingg.fep.serial.Serial;
import com.pxingg.utils.HexStringUtils;
import com.pxingg.utils.RangeUtils;

/**
 * @author pxingg
 * 
 */
public class PowerRegulation4
{
    /**
     * The log of this class
     */
    static Log log = LogFactory.getLog(PowerRegulation4.class);
    /**
     * The MIN POWER of the Power Regulation board
     */
    public final static int MIN_POWER = 0xFF;
    /**
     * The MAX POWER of the Power Regulation board
     */
    public final static int MAX_POWER = 0x00;
    /**
     * The Close flag of the Power Regulation board
     */
    public final static int CLOSE_POWER = 0x00;
    /**
     * The relay switch is ON, or the input data is ON
     */
    public static final int SWITCH_ON = 0x01;
    /**
     * The relay switch is OFF, or the input data is OFF
     */
    public static final int SWITCH_OFF = 0x00;
    /**
     * The Total number of Power Regulation Switch
     */
    public static final int TOTAL_SWITCH = 4;
    /**
     * The Total number of Input Switch
     */
    public static final int TOTAL_INPUT = 4;
    /**
     * The MIN equipment no in the SEVYTEC switch RS485 net work
     */
    public static final int MIN_EQUIPMENT_NO = 0x01;
    /**
     * The MAX equipment no in the SEVYTEC switch RS485 net work
     */
    public static final int MAX_EQUIPMENT_NO = 0xFE;
    /**
     * The MAX wait time from send request and receive response message
     */
    public static final long MAX_TIME_WAIT_INTERVAL = 500;
    /**
     * The default sleep interval for process the message
     */
    public static final long DEFAULT_SLEEP_INTERVAL = 5;
    /**
     * The default interval for switch change status
     */
    public static final long SWITCH_CHANGE_INTERVAL = 20;
    /**
     * The request message header of the production of SEVYTEC switch production
     */
    protected static final byte REQUEST_MESSAGE_HEADER[] = {
        (byte) 0xAA, (byte) 0xBB
    };
    /**
     * The response message header of the production of SEVYTEC switch production
     */
    protected static final byte RESPONSE_MESSAGE_HEADER[] = {
        (byte) 0xCC, (byte) 0xDD
    };
    /**
     * The max message length of the serial switch
     */
    protected static final int MAX_MESSAGE_LENGTH = 9;

    /**
     * The message length of the query set power out put message
     */
    protected static final int POWER_REQUEST_MESSAGE_LENGTH = 9;
    /**
     * The message length of the response set power out put message
     */
    protected static final int POWER_RESPONSE_MESSAGE_LENGTH = 3;
    /**
     * The message length of the query the status message
     */
    protected static final int STATUS_REQUEST_MESSAGE_LENGTH = 9;
    /**
     * The message length of the response query the status message
     */
    protected static final int STATUS_RESPONSE_MESSAGE_LENGTH = 9;

    // ///////////////////////////////////////////////////////////
    // The member of the switch
    // ////////////////////////////////////////////////////////////
    /**
     * The equipment no in the Switch RS485 network
     */
    protected int equipmentNo = 0;
    /**
     * The Serial bus the device connect to
     */
    protected Serial rs485Bus = null;
    /**
     * The status of the switch
     */
    protected int[] switchStatus = new int[PowerRegulation4.TOTAL_SWITCH];
    /**
     * The status of input
     */
    protected int[] inputStatus = new int[PowerRegulation4.TOTAL_INPUT];
    /**
     * Indicate if the Switch is connected to the Bus
     */
    protected boolean connected = false;
    /**
     * The buffer used to send and receive message
     */
    protected byte[] buffer = new byte[PowerRegulation4.MAX_MESSAGE_LENGTH];
    /**
     * Timer is used to calculate the interval of send and receive the message
     */
    Timer timer = new Timer();
    /**
     * Used to record lasted the error information in the operation
     */
    String errorInfo = "";

    // ///////////////////////////////////////////////////////////
    // The function of the switch
    // ////////////////////////////////////////////////////////////
    /**
     * Construction of the PowerRegulation4
     * 
     * @param equipmentNo
     *            The equipment no of the switch
     * @param rs485Bus
     *            The RS485 bus the switch connected
     */
    public PowerRegulation4(final int equipmentNo, final Serial rs485Bus)
    {
        this.equipmentNo = equipmentNo;
        this.rs485Bus = rs485Bus;
        connected = false;

        // fill the message header
        buffer[0] = PowerRegulation4.REQUEST_MESSAGE_HEADER[0];
        buffer[1] = PowerRegulation4.REQUEST_MESSAGE_HEADER[1];
        buffer[2] = (byte) this.equipmentNo;

        for (int i = 0; i < PowerRegulation4.TOTAL_SWITCH; i++)
        {
            // initial all power off
            switchStatus[i] = PowerRegulation4.CLOSE_POWER;
        }

        for (int i = 0; i < PowerRegulation4.TOTAL_INPUT; i++)
        {
            // initial all switch off
            inputStatus[i] = PowerRegulation4.SWITCH_OFF;
        }
    }

    /**
     * Connect the Switch to the Rs485 Bus
     * 
     * @throws XException
     *             if can not connect to the RS485 Bus
     */
    public void connect() throws XException
    {
        if (connected)
        {
            return;
        }
        if (rs485Bus == null)
        {
            errorInfo = "The serial bus is not set in the Serial switch";
            PowerRegulation4.log.error(errorInfo);
            throw new XException(errorInfo);
        }

        if (RangeUtils.isBeyondLPRP(PowerRegulation4.MIN_EQUIPMENT_NO, PowerRegulation4.MAX_EQUIPMENT_NO, equipmentNo))
        {
            errorInfo = "equipmentNo " + equipmentNo + " is invalid";
            PowerRegulation4.log.error(errorInfo);
            throw new XException(errorInfo);
        }

        if (!rs485Bus.isConnected())
        {
            errorInfo = "the RS485 Bus is not connected";
            PowerRegulation4.log.error(errorInfo);
            throw new XException(errorInfo);
        }

        // now send a status message to get the status of the message
        queryStatus();
        // the status message is process successful, and the board is connected
        connected = true;
    }

    /**
     * Get the input status array
     * 
     * @return the input status array
     */
    public int[] getInputStatus()
    {
        return inputStatus;
    }

    /**
     * Get the switch status array
     * 
     * @return the switch status array
     */
    public int[] getSwitchStatus()
    {
        return switchStatus;
    }

    protected void powerOn() throws XException
    {
        // 0xAA 0xBB 设备号0x01 输出1 输出2 输出3 输出4 CRC
        buffer[3] = (byte) 0x01;
        buffer[4] = (byte) switchStatus[0];
        buffer[5] = (byte) switchStatus[1];
        buffer[6] = (byte) switchStatus[2];
        buffer[7] = (byte) switchStatus[3];
        buffer[8] = CRC.sumCRC8(buffer, PowerRegulation4.STATUS_REQUEST_MESSAGE_LENGTH - 1);

        sendWithoutReceive(PowerRegulation4.POWER_REQUEST_MESSAGE_LENGTH, PowerRegulation4.MAX_TIME_WAIT_INTERVAL);
        // sleep SWITCH_CHANGE_INTERVAL macro sends to wait the message is send
        // and the switch status is changed
        TimeUtils.mySleep(PowerRegulation4.SWITCH_CHANGE_INTERVAL);
    }

    /**
     * Set the switch <b>switchNo</b> at power <b>power</b>
     * 
     * @param switchNo
     *            The switch no to set
     * @param power
     *            the power will set to the switch
     * @throws XException
     */
    public void powerOn(final int switchNo, final int power) throws XException
    {
        if (!rs485Bus.isConnected())
        {
            errorInfo = "the RS485 Bus is not connected";
            PowerRegulation4.log.error(errorInfo);
            throw new XException(errorInfo);
        }

        if (RangeUtils.isBeyondLPRP(1, PowerRegulation4.TOTAL_SWITCH, switchNo))
        {
            errorInfo = "power switch no " + switchNo + " is invalid";
            PowerRegulation4.log.error(errorInfo);
            throw new XException(errorInfo);
        }

        if (RangeUtils.isBeyondLPRP(PowerRegulation4.CLOSE_POWER, PowerRegulation4.MIN_POWER, power))
        {
            errorInfo = "power rangulation value " + power + " is invalid";
            PowerRegulation4.log.error(errorInfo);
            throw new XException(errorInfo);
        }

        switchStatus[switchNo - 1] = power;

        // send the message to change the power
        powerOn();

        // Begin to to the check of the command
        // queryStatus();
        if (switchStatus[switchNo - 1] != power)
        {
            errorInfo = "on the equiment no " + equipmentNo + " try to send message to set power  to " + power + ", but the return power is " + switchStatus[switchNo - 1];
            PowerRegulation4.log.error(errorInfo);
            throw new XException(errorInfo);
        }
    }

    /**
     * Set the switch <b>switchNo</b> at power <b>power</b> at the defined <b>speed</b>
     * 
     * @param switchNo
     *            The switch no to set
     * @param power
     *            the power will set to the switch
     * @param speed
     *            the execute speed
     * @throws XException
     */
    public void powerSlowOn(final int switchNo, final int power, final int speed) throws XException
    {
        if (!rs485Bus.isConnected())
        {
            errorInfo = "the RS485 Bus is not connected";
            PowerRegulation4.log.error(errorInfo);
            throw new XException(errorInfo);
        }

        if (RangeUtils.isBeyondLPRP(1, PowerRegulation4.TOTAL_SWITCH, switchNo))
        {
            errorInfo = "power switch no " + switchNo + " is invalid";
            PowerRegulation4.log.error(errorInfo);
            throw new XException(errorInfo);
        }

        if (RangeUtils.isBeyondLPRP(PowerRegulation4.CLOSE_POWER, PowerRegulation4.MIN_POWER, power))
        {
            errorInfo = "power rangulation value " + power + " is invalid";
            PowerRegulation4.log.error(errorInfo);
            throw new XException(errorInfo);
        }

        switchStatus[switchNo - 1] = power;

        if (RangeUtils.isBeyondLPRP(PowerRegulation4.CLOSE_POWER, PowerRegulation4.MIN_POWER, speed))
        {
            errorInfo = "power rangulation speed value " + speed + " is invalid";
            PowerRegulation4.log.error(errorInfo);
            throw new XException(errorInfo);
        }

        switchStatus[switchNo - 1] = power;

        // 0xAA 0xBB 设备号 0x02 输出编号 目标功率档位 渐变输出的时间参数 0x00 CRC
        buffer[3] = (byte) 0x02;
        buffer[4] = (byte) switchNo;
        buffer[5] = (byte) power;
        buffer[6] = (byte) speed;
        buffer[7] = (byte) 0x00;
        buffer[8] = CRC.sumCRC8(buffer, PowerRegulation4.STATUS_REQUEST_MESSAGE_LENGTH - 1);

        sendWithoutReceive(PowerRegulation4.POWER_REQUEST_MESSAGE_LENGTH, PowerRegulation4.MAX_TIME_WAIT_INTERVAL);
        // sleep SWITCH_CHANGE_INTERVAL macro sends to wait the message is send
        // and the switch status is changed
        TimeUtils.mySleep(PowerRegulation4.SWITCH_CHANGE_INTERVAL);
    }

    /**
     * Get the status of all the switch and input
     * 
     * @throws XException
     */
    public void queryStatus() throws XException
    {
        if (!rs485Bus.isConnected())
        {
            errorInfo = "the RS485 Bus is not connected";
            PowerRegulation4.log.error(errorInfo);
            throw new XException(errorInfo);
        }
        // 0xAA 0xBB 设备号 0x0D 0x01 0x00 0x00 0x00 CRC
        buffer[3] = (byte) 0x0D;
        buffer[4] = (byte) 0x01;
        buffer[5] = (byte) 0x00;
        buffer[6] = (byte) 0x00;
        buffer[7] = (byte) 0x00;
        buffer[8] = CRC.sumCRC8(buffer, PowerRegulation4.STATUS_REQUEST_MESSAGE_LENGTH - 1);

        final byte[] response = sendAndReceive(PowerRegulation4.STATUS_REQUEST_MESSAGE_LENGTH, PowerRegulation4.STATUS_RESPONSE_MESSAGE_LENGTH);

        if (response == null)
        {
            errorInfo = "send the status request message, but can not get the response message";
            PowerRegulation4.log.error(errorInfo);
            throw new XException(errorInfo);
        }

        if (response[0] != PowerRegulation4.RESPONSE_MESSAGE_HEADER[0] || response[1] != PowerRegulation4.RESPONSE_MESSAGE_HEADER[1])
        {
            errorInfo = "the header of response data(" + HexStringUtils.hex2String(response, ", ", 0, response.length) + ") is invalid";
            PowerRegulation4.log.error(errorInfo);
            throw new XException(errorInfo);
        }

        final int equipNo = (response[2] & 0xFF);
        if (equipNo != equipmentNo)
        {
            errorInfo = "the reponse equipment no " + equipNo + " is not expected equipment no " + equipmentNo;
            PowerRegulation4.log.error(errorInfo);
            throw new XException(errorInfo);
        }

        if (CRC.sumCRC8(response, PowerRegulation4.STATUS_RESPONSE_MESSAGE_LENGTH - 1) != response[response.length - 1])
        {
            errorInfo = "the data(" + HexStringUtils.hex2String(response, ", ", 0, response.length) + ") is invalid for the CRC is not match";
            PowerRegulation4.log.error(errorInfo);
            throw new XException(errorInfo);
        }

        // analysis the status of the switch
        for (int i = 0; i < 4; i++)
        {
            // 0x00~0xff0 表示板子功率从最大到最小
            switchStatus[i] = response[3 + i] & 0xFF;
        }

        // analysis the status of the input
        final int status = response[7];
        for (int i = 0; i < PowerRegulation4.TOTAL_INPUT; i++)
        {
            if ((status & (0x01 << i)) > 0)
            {
                inputStatus[i] = PowerRegulation4.SWITCH_ON;
            }
            else
            {
                inputStatus[i] = PowerRegulation4.SWITCH_OFF;
            }
        }
        // sleep SWITCH_CHANGE_INTERVAL macro sends to wait the message is send
        // and the switch status is changed
        TimeUtils.mySleep(PowerRegulation4.SWITCH_CHANGE_INTERVAL);
    }

    /**
     * Send the message to the switch and try to get the response in a defined interval
     * 
     * @param requestLength
     *            the length in the buffer need to send
     * @param responseLength
     *            the except length of the response message
     * @return
     */
    protected byte[] sendAndReceive(final int requestLength, final int responseLength)
    {
        byte[] response = null;

        synchronized (rs485Bus)
        {
            // clear the data in the send and receive buffer first
            rs485Bus.getSendBuffer().clear();
            rs485Bus.getReceiveBuffer().clear();
            // put the data in the buffer and begin the timer
            rs485Bus.getSendBuffer().append(buffer, requestLength);
            timer.start();

            while (true)
            {
                response = rs485Bus.getReceiveBuffer().serve(responseLength);
                if (response != null)
                {
                    // have get the response
                    break;
                }
                if (timer.getInterval() > PowerRegulation4.MAX_TIME_WAIT_INTERVAL)
                {
                    // have wait enough time, but the response is not response
                    // clear the message that not send, and will not send it
                    // again
                    connected = false;
                    rs485Bus.getSendBuffer().clear();
                    errorInfo = "have wait " + PowerRegulation4.MAX_TIME_WAIT_INTERVAL + " macro seconds, but the response of the message is not get";
                    PowerRegulation4.log.error(errorInfo);
                    break;
                }

                TimeUtils.mySleep(PowerRegulation4.DEFAULT_SLEEP_INTERVAL);
            }
        }

        return response;
    }

    /**
     * Send the message to the switch and without receive message
     * 
     * @param requestLength
     *            the length in the buffer need to send
     * @param interval
     *            the wait interval for the message to send
     * @throws XException
     */
    protected void sendWithoutReceive(final int requestLength, final long interval) throws XException
    {
        synchronized (rs485Bus)
        {
            // clear the data in the send and receive buffer first
            rs485Bus.getSendBuffer().clear();
            rs485Bus.getReceiveBuffer().clear();
            // put the data in the buffer and begin the timer
            rs485Bus.getSendBuffer().append(buffer, requestLength);

            timer.start();
            while (rs485Bus.getSendBuffer().size() > 0)
            {
                if (timer.getInterval() > interval)
                {
                    // clear the message that not send, and will not send it
                    // again
                    connected = false;
                    rs485Bus.getSendBuffer().clear();
                    errorInfo = "have wait " + interval + " macro seconds, but the message is not complete send";
                    PowerRegulation4.log.error(errorInfo);
                    throw new XException(errorInfo);
                }
                TimeUtils.mySleep(PowerRegulation4.DEFAULT_SLEEP_INTERVAL);
            }
        }
    }

    /**
     * (non-Javadoc)
     * 
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString()
    {
        return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
    }
}
